<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="../iron-icons/iron-icons.html">
<link rel="import" href="../paper-button/paper-button.html">
<link rel="import" href="../paper-input/paper-input.html">
<link rel="import" href="../paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="../tf-graph-common/tf-graph-common.html">
<link rel="import" href="tf-graph-scene.html">
<link rel="import" href="tf-graph-params.html">
<dom-module id="tf-graph">
<template>
<style>
.container {
  width: 100%;
  height: 100%;
  background: white;
  box-shadow: 0 1px 5px rgba(0,0,0,0.2);
}

.vertical {
  width:100%;
  height:100%;
  @apply(--layout-vertical);
}

.auto {
  @apply(--layout-flex-auto);
  @apply(--layout-vertical);
}

h2 {
  text-align: center;
}

paper-button {
  text-transform: none;
}
</style>
<div class="container">
  <tf-graph-params id="graphParams"></tf-graph-params>
  <div class="vertical">
    <h2>[[title]]</h2>
    <tf-graph-scene id="scene" class="auto"
          render-hierarchy="[[renderHierarchy]]"
          highlighted-node="[[_getVisible(highlightedNode)]]"
          selected-node="[[selectedNode]]"
          color-by="[[colorBy]]"
          name="[[graphName]]"
          progress="[[progress]]"
    ></tf-graph-scene>
  </div>
</div>
</template>
</dom-module>

<script>
Polymer({

  is: 'tf-graph',

  properties: {
    graphHierarchy: {
      type: Object,
      notify: true,
      observer: '_graphChanged'
    },
    basicGraph: Object,
    hierarchyParams: Object,
    progress: {
      type: Object,
      notify: true,
    },
    title: String,
    selectedNode: {
      type: String,
      notify: true,
    },
    highlightedNode: {
      type: String,
      notify: true
    },
    /** What to color the nodes by (compute time, memory, device etc.) */
    colorBy: String,
    colorByParams: {
      type: Object,
      notify: true,
      readOnly: true, // Produces and doesn't consume.
    },
    renderHierarchy: {
      type: Object,
      readOnly: true,
      notify: true,
    },
    // internal properties
    _graphParams: {
      type: Object,
      value: function() {
        return this.$.graphParams;
      }
    },
    _renderDepth: {
      type: Number,
      value: 1
    },
    _allowGraphSelect: {
      type: Boolean,
      value: true
    },
  },
  observers: [
    '_buildRenderHierarchy(graphHierarchy, _graphParams)'
  ],
  _buildRenderHierarchy: function(graphHierarchy, params) {
    tf.time('new tf.graph.render.Hierarchy', function() {
      if (graphHierarchy.root.type !== tf.graph.NodeType.META) {
        // root must be metanode but sometimes Polymer's dom-if has not
        // remove tf-graph element yet in <tf-node-info>
        // and thus mistakenly pass non-metanode to this module.
        return;
      }
      var renderGraph = new tf.graph.render.RenderGraphInfo(graphHierarchy,
          params);
      // Producing the 'color by' parameters to be consumed
      // by the tf-graph-controls panel. It contains information about the
      // min and max values and their respective colors, as well as list
      // of devices with their respective colors.

      function getColorParamsFromScale(scale) {
        return {
          minValue: scale.domain()[0],
          maxValue: scale.domain()[1],
          startColor: scale.range()[0],
          endColor: scale.range()[1]
        };
      }

      this._setColorByParams({
        compute_time: getColorParamsFromScale(renderGraph.computeTimeScale),
        memory: getColorParamsFromScale(renderGraph.memoryUsageScale),
        device: _.map(renderGraph.deviceColorMap.domain(),
            function(deviceName) {
          return {
            device: deviceName,
            color: renderGraph.deviceColorMap(deviceName)
          };
        })
      });
      this._setRenderHierarchy(renderGraph);
    }.bind(this));
  },
  _getVisible: function(name) {
    if (!name) {
      return name;
    }
    return this.renderHierarchy.getNearestVisibleAncestor(name);
  },
  listeners: {
    'graph-select': '_graphSelected',
    'disable-click': '_disableClick',
    'enable-click': '_enableClick',
    // Nodes
    'node-toggle-expand': '_nodeToggleExpand',
    'node-select': '_nodeSelected',
    'node-highlight': '_nodeHighlighted',
    'node-unhighlight': '_nodeUnhighlighted',
    'node-toggle-extract': '_nodeToggleExtract',
    'node-toggle-seriesgroup': '_nodeToggleSeriesGroup',

    // Annotations

    /* Note: currently highlighting/selecting annotation node has the same
      * behavior as highlighting/selecting actual node so we point to the same
      * set of event listeners.  However, we might redesign this to be a bit
      * different.
      */
    'annotation-select': '_nodeSelected',
    'annotation-highlight': '_nodeHighlighted',
    'annotation-unhighlight': '_nodeUnhighlighted',
  },
  _graphChanged: function() {
    // When a new graph is loaded, fire this event so that there is no
    // info-card being displayed for the previously-loaded graph.
    this.fire('graph-select');
  },
  _graphSelected: function(event) {
    // Graph selection is not allowed during an active zoom event, as the
    // click seen during a zoom/pan is part of the zooming and does not
    // indicate a user desire to click on a specific section of the graph.
    if (this._allowGraphSelect) {
      this.set('selectedNode', null);
    }
    // Reset this variable as a bug in d3 zoom behavior can cause zoomend
    // callback not to be called if a right-click happens during a zoom event.
    this._allowGraphSelect = true;
  },
  _disableClick: function(event) {
    this._allowGraphSelect = false;
  },
  _enableClick: function(event) {
    this._allowGraphSelect = true;
  },
  _nodeSelected: function(event) {
    if (this._allowGraphSelect) {
      this.set('selectedNode', event.detail.name);
    }
    // Reset this variable as a bug in d3 zoom behavior can cause zoomend
    // callback not to be called if a right-click happens during a zoom event.
    this._allowGraphSelect = true;
  },
  _nodeHighlighted: function(event) {
    this.set('highlightedNode', event.detail.name);
  },
  _nodeUnhighlighted: function(event) {
    this.set('highlightedNode', null);
  },
  _nodeToggleExpand: function(event) {
    // Immediately select the node that is about to be expanded.
    this._nodeSelected(event);

    // Compute the sub-hierarchy scene.
    var nodeName = event.detail.name;
    var renderNode = this.renderHierarchy.getRenderNodeByName(nodeName);
    // Op nodes are not expandable.
    if (renderNode.node.type === tf.graph.NodeType.OP) {
      return;
    }
    this.renderHierarchy.buildSubhierarchy(nodeName);
    renderNode.expanded = !renderNode.expanded;

    // Expand the node with some delay so that the user can immediately see
    // the visual effect of selecting that node, before the expansion is
    // done.
    this.async(function() {
      this.querySelector('#scene').setNodeExpanded(renderNode);
    }, 75);
  },
  _nodeToggleExtract: function(event) {
    // Toggle the include setting of the specified node appropriately.
    var nodeName = event.detail.name;
    var renderNode = this.renderHierarchy.getRenderNodeByName(nodeName);
    if (renderNode.node.include == tf.graph.InclusionType.INCLUDE) {
      renderNode.node.include = tf.graph.InclusionType.EXCLUDE;
    } else if (renderNode.node.include == tf.graph.InclusionType.EXCLUDE) {
      renderNode.node.include = tf.graph.InclusionType.INCLUDE;
    } else {
      renderNode.node.include =
       this.renderHierarchy.isNodeAuxiliary(renderNode)
          ? tf.graph.InclusionType.INCLUDE : tf.graph.InclusionType.EXCLUDE;
    }

    // Rebuild the render hierarchy.
    this._buildRenderHierarchy(this.graphHierarchy, this._graphParams);
  },
  _nodeToggleSeriesGroup: function(event) {
    // Toggle the group setting of the specified node appropriately.
    var nodeName = event.detail.name;
    tf.graph.toggleNodeSeriesGroup(this.hierarchyParams.seriesMap, nodeName);

    // Rebuild the render hierarchy with the updated series grouping map.
    this.set('progress', {
      value: 0,
      msg: ''
    });
    var tracker = tf.getTracker(this);
    var hierarchyTracker = tf.getSubtaskTracker(tracker, 100,
          'Namespace hierarchy');
    tf.graph.hierarchy.build(this.basicGraph, this.hierarchyParams, hierarchyTracker)
    .then(function(graphHierarchy) {
      this.set('graphHierarchy', graphHierarchy);
      this._buildRenderHierarchy(this.graphHierarchy, this._graphParams);
    }.bind(this));
  },
  not: function(x) {
    return !x;
  }
});
</script>
